#include "scrull.h"

MODULE_LICENSE("GNU GPL v3");

static int scrull_major = 0;
static int scrull_minor = 0;
static int scrull_nr_devs = 4;
static int scrull_quantum = SCRULL_QUANTUM;
static int scrull_qset = SCRULL_QSET;
module_param(scrull_major, int, S_IRUGO);
module_param(scrull_minor, int, S_IRUGO);
module_param(scrull_nr_devs, int, S_IRUGO);
module_param(scrull_quantum, int, S_IRUGO);
module_param(scrull_qset, int, S_IRUGO);

struct file_operations scrull_fops = {
    .owner = THIS_MODULE,
    .llseek = scrull_llseek,
    .read = scrull_read,
    .write = scrull_write,
    .ioctl = scrull_ioctl,
    .open = scrull_open,
    .release = scrull_release,
};

static void scrull_setup_cdev(struct scrull_dev *dev, int index)
{
    int err, devno = MKDEV(scrull_major, scrull_minor + index);

    cdev_init(&dev->cdev, &scrull_fops);
    dev->cdev.owner = THIS_MODULE;
    dev->cdev.ops = &scrull_fops;
    err = cdev_add(&dev->cdev, devno, 1);

    if (err)
        printk(KERN_NOTICE "Error %d adding scrull%d", err, index);
}


int scrull_open(struct inode *inode, struct file *filp)
{
    struct scrull_dev *dev;

    dev = container_of(inode->i_cdev, struct scrull_dev, cdev);
    filp->private_data = dev;
    
    if ((filp->f_flags & O_ACCMODE) == O_WRONLY) {
        scrull_trim(dev);
    }

    return 0;
}

int scrull_release(struct inode *inode, struct file *filp)
{
    return 0;
}


int scrull_trim(struct scrull_dev *dev)
{
    struct scrull_qset *next, *dptr;
    int qset = dev->qset;
    int i;

    for (dptr = dev->data; dptr; dptr = next) {
        if (dptr->data) {
            for (i = 0; i < qset; i++)
                kfree(dptr->data[i]);
            kfree(dptr->data);
            dptr->data = NULL;
        }
        next = dptr->next;
        kfree(dptr);
    }
    dev->size = 0;
    dev->quantum = scrull_quantum;
    dev->qset = scrull_qset;
    dev->data = NULL;

    return 0;
}

ssize_t scrull_read(struct file *filp, char __user *buf, size_t count, loff_t *f_pos)
{
    struct scrull_dev *dev = filp->private_data;
    struct scrull_qset *dptr;
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset;
    int item, s_pos, q_pos, rest;
    ssize_t retval =  0;

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    if (*f_pos >= dev->size)
        goto out;

    if (*f_pos + count > dev->size)
        count = dev->size - *f_pos;

    item = (long)(*f_pos) / itemsize; // 此处先将f_pos转换为32位是因为64位除法要用到__divdi3，需要用do_div，否则会出现找不到符号
    rest = (long)(*f_pos) % itemsize; // __moddi3
    s_pos = rest / quantum;
    q_pos = rest % quantum;

    dptr = scrull_follow(dev, item);
    
    if (dptr == NULL || !dptr->data || !dptr->data[s_pos])
        goto out;

    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_to_user(buf, dptr->data[s_pos] + q_pos, count)) {
        retval = -EFAULT;
        goto out;
    }

    *f_pos += count;
    retval = count;
    
out:
    up(&dev->sem);
    return retval;
}
        
ssize_t scrull_write(struct file *filp, const char __user *buf, size_t count, loff_t *f_pos)
{
    struct scrull_dev *dev = filp->private_data;
    struct scrull_qset *dptr;
    int quantum = dev->quantum, qset = dev->qset;
    int itemsize = quantum * qset;
    int item, s_pos, q_pos, rest;
    ssize_t retval = -ENOMEM;

    if (down_interruptible(&dev->sem))
        return -ERESTARTSYS;

    item = (long)(*f_pos) / itemsize;
    rest = (long)(*f_pos) % itemsize;
    s_pos = rest / quantum;
    q_pos = rest % quantum;

    dptr = scrull_follow(dev, item);
    if (dptr == NULL)
        goto out;

    if (!dptr->data) {
        dptr->data = kmalloc(qset * sizeof(char *), GFP_KERNEL);
        if (!dptr->data)
            goto out;
        memset(dptr->data, 0, qset * sizeof(char *));
    }

    if (!dptr->data[s_pos]) {
        dptr->data[s_pos] = kmalloc(quantum, GFP_KERNEL);
        if (!dptr->data[s_pos])
            goto out;
    }

    if (count > quantum - q_pos)
        count = quantum - q_pos;

    if (copy_from_user(dptr->data[s_pos] + q_pos, buf, count)) {
        retval = -EFAULT;
        goto out;
    }
    *f_pos += count;
    retval = count;

    if (dev->size < *f_pos)
        dev->size = *f_pos;

out:
    up(&dev->sem);
    return retval;
}

int __init scrull_init(void)
{
    dev_t dev;
    struct scrull_dev *s_dev;
    int result, i;

    if (scrull_major) {
        dev = MKDEV(scrull_major, scrull_minor);
        result = register_chrdev_region(dev, scrull_nr_devs, "scrull");
    } else {
        result = alloc_chrdev_region(&dev, scrull_minor, scrull_nr_devs, "scrull");
        scrull_major = MAJOR(dev);
    }
    
    if (result == 0) {
        for (i = 0; i < scrull_nr_devs; i++) {
            s_dev = 
            scrull_setup_cdev(dev, i);
        }
    }

    return result;
}

void __exit scrull_exit(void)
{
    // 删除每个scrull_dev中的数据
    return;
}

module_init(scrull_init);
module_exit(scrull_exit);
